package com.ling.pl.core.context;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ling.pl.core.commons.exception.BusinessException;
import com.ling.pl.core.commons.utils.AssertHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.util.ReflectionUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.net.URLEncoder;

public abstract class BaseContextSecurity implements ContextSecurity {
    public static final int TWO_WEEKS_S = 1209600;
    protected final Log logger = LogFactory.getLog(getClass());
    @Value("${ling2.cookieDomain:}")
    String cookieDomain;
    @Value("${ling2.enablecookieDomain:false}")
    Boolean enablecookieDomain;
    //
//    @Value("${ling2.cookieuserinfo.key}")
//    private String key;
    private Boolean useSecureCookie = null;
    private Method setHttpOnlyMethod = ReflectionUtils.findMethod(Cookie.class, "setHttpOnly", boolean.class);

    /**
     * token部分 模拟登录
     *
     * @param token
     * @param request
     * @param response
     */
    public void login(Token token, HttpServletRequest request, HttpServletResponse response) {
        updateToken(token, request, response);
    }

    /**
     * 更新token信息
     *
     * @param token
     * @param request
     * @param response
     */
    protected void updateToken(Token token, HttpServletRequest request, HttpServletResponse response) {
        //将用户轻量级信息+过期时间加密成可解密的token
        String tokenStr = encodeToken(JSON.toJSONString(token));
        //将token写入cookie
//        long expiryTime = System.currentTimeMillis();
//        // SEC-949
//        expiryTime += 1000L * (getTokenValiditySeconds());
        long expiryTime = 0l;//0表示关闭浏览器cookie失效
        addCookie(tokenStr, (int) expiryTime, request, response);
        //将token写入到当前容器的session
        HttpSession session = request.getSession();
        session.setAttribute(cookieTokenKey, tokenStr);
        //使用token作为key将用户更多信息吸入到redis等缓存
    }

    /**
     * 用于容器的filter调用
     *
     * @param request
     * @param response
     */
    public void filter(HttpServletRequest request, HttpServletResponse response) {
        boolean isUserLogin = isLogin(request, response);
        if (!isUserLogin) {//未登录
            String tokenStr = getTokenCookie(request);
            if (AssertHelper.notEmpty(tokenStr)) {//有token
                Token token = JSONObject.parseObject(decodeToken(tokenStr), Token.class);
                if (isValid(token)) {//过期
                    logout(request, response);
                } else {//未过期,模拟登录
                    token.setUpdateTime(System.currentTimeMillis());
                    login(token, request, response);
                }
            }
        } else {//已登录
            String tokenStr = getTokenCookie(request);
            if (AssertHelper.notEmpty(tokenStr)) {//有token
                //2个请求,先请求后响应时,后响应的token会覆盖先响应的token,需要在页面上刷新浏览器当前页面解决,比如JavaScript上通过window.url=index解决,这里不再处理
                //允许token相同版本号上updateTime的时间误差(2个请求,先请求后响应时,后响应的token会覆盖先响应的token)
                //始终用页面的cookie覆盖session和浏览器的cookie信息
                try {
                    tokenStr = URLDecoder.decode(tokenStr, "UTF-8");
                } catch (UnsupportedEncodingException e) {
//                    e.printStackTrace();
                }
                Token token = JSONObject.parseObject(decodeToken(tokenStr), Token.class);
                if (isValid(token)) {//过期
                    logout(request, response);
                } else {//未过期
                    token.setUpdateTime(System.currentTimeMillis());
                    updateToken(token, request, response);
                }
            }
        }
    }

    /**
     * token部分 模拟登出
     */
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response) {
        cancelCookie(request, response);
        HttpSession session = request.getSession();
        session.removeAttribute(cookieTokenKey);
    }

    ///todo 这里需要设置token过期时间,需要写到文档说明中
    protected int getTokenValiditySeconds() {
        return TWO_WEEKS_S;
    }

    /**
     * 过期为true,没过期为false
     *
     * @param token
     * @return
     */
    public boolean isValid(Token token) {
        long lastTime = token.getUpdateTime() > 0 ? token.getUpdateTime() : token.getCreationTime();
        return lastTime + getTokenValiditySeconds() < System.currentTimeMillis();
    }

    /**
     * Calculates the digital signature to be put in the cookie. Default value is
     * MD5 ("username:tokenExpiryTime:password:key")
     */
    protected String encodeToken(String token) {
        String encode = new String(Hex.encode(token.getBytes()));
        logger.debug(token + "==>" + encode);
        return encode;
//        return  token;
    }

    protected String decodeToken(String token) {

        if (!Base64.isBase64(token.getBytes())) {
            throw new BusinessException("Cookie token was not Base64 encoded; value was '" + token + "'");
        }

        String cookieAsPlainText = new String(Hex.decode(token));
        logger.debug(token + "==>" + cookieAsPlainText);
        return cookieAsPlainText;
    }

    /**
     * 添加cookie
     *
     * @param cookieValue
     * @param maxAge
     * @param request
     * @param response
     */
    protected void addCookie(String cookieValue, int maxAge, HttpServletRequest request, HttpServletResponse response) {
        Cookie cookie = getCookie(cookieTokenKey, cookieValue, maxAge, request, response);
        response.addCookie(cookie);
    }

    /**
     * Sets a "cancel cookie" (with maxAge = 0) on the response to disable persistent logins.
     */
    protected void cancelCookie(HttpServletRequest request, HttpServletResponse response) {
        logger.debug("Cancelling cookie");
        Cookie cookie = new Cookie(cookieTokenKey, null);
        cookie.setMaxAge(0);

        cookie.setPath(getCookiePath(request));
        if (enablecookieDomain && AssertHelper.notEmpty(cookieDomain)) {
            cookie.setDomain(cookieDomain);
        }

        response.addCookie(cookie);
    }

    /**
     * @param cookieName
     * @param cookieValue
     * @param maxAge      如果不设置Expires的属性那么Cookie的存活时间就是在关闭浏览器的时候。
     * @param request
     * @param response
     * @return
     */
    protected Cookie getCookie(String cookieName, String cookieValue, int maxAge, HttpServletRequest request, HttpServletResponse response) {
        if (AssertHelper.notEmpty(cookieValue)) {
            try {
                cookieValue = URLEncoder.encode(cookieValue, "UTF-8");
//				System.out.println(URLDecoder.decode(cookieValue));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        Cookie cookie = new Cookie(cookieName, cookieValue);
        if (maxAge > 0) {
            cookie.setMaxAge(maxAge);
        }
        cookie.setPath(getCookiePath(request));
        if (AssertHelper.notEmpty(cookieDomain)) {
            cookie.setDomain(cookieDomain);
        }
        if (useSecureCookie == null) {
            cookie.setSecure(request.isSecure());
        } else {
            cookie.setSecure(useSecureCookie);
        }

        if (setHttpOnlyMethod != null) {
            ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
        } else if (logger.isDebugEnabled()) {
            logger.debug("Note: Cookie will not be marked as HttpOnly because you are not using Servlet 3.0 (Cookie#setHttpOnly(boolean) was not found).");
        }
        return cookie;
    }

    protected String getCookiePath(HttpServletRequest request) {
        String contextPath = request.getContextPath();
        return contextPath.length() > 0 ? contextPath : "/";
    }

    /**
     * 从cookie中获取加密的用户信息
     *
     * @param request
     * @return
     */
    protected String getTokenCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();

        if ((cookies == null) || (cookies.length == 0)) {
            return null;
        }

        for (Cookie cookie : cookies) {
            if (cookieTokenKey.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }

        return null;
    }
}
